package com.music.glide.load.data

import android.util.Log
import com.music.glide.KPriority
import com.music.glide.load.KDataSource
import com.music.glide.load.KHttpException
import com.music.glide.load.model.KGlideUrl
import com.music.glide.util.KContentLengthInputStream
import com.music.glide.util.LogTime
import java.io.IOException
import java.io.InputStream
import java.net.HttpURLConnection
import java.net.URL


class KHttpUrlFetcher constructor(
    private val glideUrl: KGlideUrl,
    private val timeout: Int,
    private val connectionFactory: HttpUrlConnectionFactory = DefaultHttpUrlConnectionFactory()
) : KDataFetcher<InputStream> {

    private var urlConnect: HttpURLConnection? = null
    private var stream: InputStream? = null
    @Volatile
    private var isCancelled = false

    override fun loadData(
        priority: KPriority, callback: KDataFetcher.DataCallback<in InputStream>
    ) {
        val startTime = LogTime.getLogTime()
        try {
            loadDataWithRedirects(
                glideUrl.toURL(), 0, null, glideUrl.getHeader()
            ).let {
                if (it != null) {
                    callback.onDataReady(it)
                } else {
                    callback.onLoadFailed(IOException("stream is null"))
                }
            }
        } catch (e: Exception) {
            callback.onLoadFailed(e)
        } finally {
            Log.v(TAG, "Finished http url fetcher fetch in ${LogTime.getElapsedMillis(startTime)}")
        }
    }

    @Throws(IOException::class)
    private fun loadDataWithRedirects(
        url: URL,
        redirects: Int,
        lastURL: URL? = null,
        headers: Map<String, String>
    ):InputStream? {
        if (redirects >= MAX_REDIRECT) {
            throw KHttpException("Too many $MAX_REDIRECT redirects")
        } else {
            try {
                if (lastURL?.toURI() == url.toURI()) {
                    throw KHttpException("In re-direct loop")
                }
            } catch (e: Exception) {

            }
        }
        urlConnect = connectionFactory.build(url)
        //这里的主动赋值便于后续cleanup的关闭
        stream = urlConnect?.apply {
            headers.forEach {
                addRequestProperty(it.key, it.value)
            }
            connectTimeout = timeout
            readTimeout = timeout
            useCaches = false
            doInput = true
            //不自动重定向url
            instanceFollowRedirects = false
            connect()
        }?.inputStream
        if (isCancelled) {
            return null
        }
        val statusCode = urlConnect!!.responseCode
        if (isHttpOk(statusCode)) {
            return getStreamForSuccessfulRequest(urlConnect!!)
        } else if (isHttpRedirect(statusCode)) {
            val redirectUrlString = urlConnect!!.getHeaderField("Location")
            if (redirectUrlString.isNullOrEmpty()) {
                throw KHttpException("Received empty or null redirect url")
            }
            //如果 redirectUrlString 是绝对 URL（如 https://www.example.com/path），直接解析为新的 URL。
            //如果 redirectUrlString 是相对 URL（如 /path），则会基于原始请求的 URL url 拼接成完整的绝对 URL。
            val redirectUrl = URL(url, redirectUrlString)
            cleanup()
            return loadDataWithRedirects(redirectUrl, redirects + 1, url, headers)
        } else if (statusCode == INVALID_STATUS_CODE) {
            throw KHttpException(statusCode)
        } else {
            throw KHttpException(urlConnect!!.responseMessage, statusCode)
        }
    }
    //多次调用urlConnection.inputStream会返回相同对象,但是读取可能会受到影响
    private fun getStreamForSuccessfulRequest(urlConnection: HttpURLConnection):InputStream? {
        if (urlConnection.contentEncoding.isNullOrEmpty()) {
            val contentLength = urlConnection.contentLength
            stream = KContentLengthInputStream.obtain(urlConnection.inputStream, contentLength.toLong())
        } else {
            Log.d(TAG, "Got non empty content encoding: ${urlConnection.contentEncoding}")
            stream = urlConnection.inputStream
        }
        return stream
    }

    override fun cleanup() {
        if (stream !=null){
            kotlin.runCatching { stream!!.close() }
        }
        if (urlConnect!=null){
            urlConnect!!.disconnect()
        }
        urlConnect = null
    }

    override fun cancel() {
        isCancelled = true
    }

    override fun getDataClass(): Class<InputStream> {
        return InputStream::class.java
    }

    override fun getDataSource(): KDataSource {
        return KDataSource.REMOTE
    }

    interface HttpUrlConnectionFactory {
        @Throws(IOException::class)
        fun build(url: URL): HttpURLConnection
    }

    private class DefaultHttpUrlConnectionFactory : HttpUrlConnectionFactory {
        override fun build(url: URL): HttpURLConnection {
            return url.openConnection() as HttpURLConnection
        }
    }

    companion object {

        const val TAG = "KHttpUrlFetcher"

        const val MAX_REDIRECT = 5

        const val INVALID_STATUS_CODE = -1

        private fun isHttpOk(statusCode: Int): Boolean {
            return statusCode / 100 == 2
        }

        private fun isHttpRedirect(statusCode:Int):Boolean {
            return statusCode / 100 == 3
        }
    }
}